// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Compiler.cpp
 *
 *  Created on: Nov 24, 2009
 *      Author: dhovemey
 */

#include <cstdio> // debugging
#include <cassert>
#include "util/InputStreamReader.h" // used by compileSource() method
#include "vm/Field.h"
#include "vm/Method.h"
#include "vm/MethodAssembler.h"
#include "vm/Class.h"
#include "vm/Opcode.h"
#include "vm/ConstantPool.h"
#include "vm/Magic.h"
#include "vm/CodeBase.h"
#include "fe/SemanticAnalysisException.h"
#include "fe/ASTNode.h"
#include "fe/yyparse.h" // for operator token values
#include "fe/Parser.h" // used by compileSource() method
#include "fe/Compiler.h"

//#define DEBUG_VARREF

// ----------------------------------------------------------------------
// LocalVariableScope class
// ----------------------------------------------------------------------

LocalVariableScope::LocalVariableScope(int8_t start)
	: m_nextLocal(start)
{

}

LocalVariableScope::~LocalVariableScope()
{

}

int8_t LocalVariableScope::addLocalVar(const std::wstring &varName, const Location &varLoc)
{
	if (unsigned(m_nextLocal) + 1 > METHOD_MAX_LOCALS) {
		throw SemanticAnalysisException(L"Too many local variables", varLoc);
	}

	if (localVarDefined(varName)) {
		throw SemanticAnalysisException(L"Redefinition of local variable " + varName, varLoc);
	}

	// allocate and add to map
	int8_t local = m_nextLocal;
	m_localVarMap[varName] = local;
	m_nextLocal++;

	return local;
}

int8_t LocalVariableScope::getLocalVar(const std::wstring &varName) const
{
	LocalVarMap::const_iterator i = m_localVarMap.find(varName);
	assert(i != m_localVarMap.end());
	return (*i).second;
}

bool LocalVariableScope::localVarDefined(const std::wstring &varName) const
{
	return m_localVarMap.find(varName) != m_localVarMap.end();
}

unsigned LocalVariableScope::getMaxLocalVars() const
{
	return unsigned(m_nextLocal);
}

// ----------------------------------------------------------------------
// ClassAndMethodContext class
// ----------------------------------------------------------------------

ClassAndMethodContext::ClassAndMethodContext(const std::wstring &clsName, CodeBase *vm, ASTNode *clsASTNode)
	: m_cls(vm->createClass(clsName))
	, m_clsASTNode(clsASTNode)
	, m_methodAsm(0)
	, m_endOfParameters(false)
{

}

ClassAndMethodContext::~ClassAndMethodContext()
{
}

void ClassAndMethodContext::enterMethod(const std::wstring &methodName, uint32_t flags, const Location &methodLoc)
{
	// Create a new MethodAssembler
	assert(m_methodAsm == 0);
	m_methodAsm = new MethodAssembler(methodName, flags);
	m_endOfParameters = false;

	m_numLocals = 0;
	assert(m_scopeStack.size() == 0);
	enterScope();
	m_scopeStack.back()->addLocalVar(L"self", methodLoc); // local 0 is always the receiver object
}

void ClassAndMethodContext::leaveMethod()
{
	// If the body didn't end in a return statement,
	// add code to return a nil value.
	MethodAssembler *methAsm = getMethodAssembler();
	if(methAsm->getLastOpcode() != OP_RETURN){
		methAsm->emitInstruction(OP_PUSH_NIL);
		methAsm->emitInstruction(OP_RETURN);
	}

	// Leave scope of method body.
	leaveScope();
	assert(m_scopeStack.empty());

	// Set total number of local variables for method.
	m_methodAsm->setNumLocals(uint8_t(m_numLocals));

	// Complete method, and delete MethodAssembler
	assert(m_methodAsm != 0);
	m_methodAsm->finish();
	Method *method = m_methodAsm->getMethod();
	m_cls->addAndAdoptMethod(method);
	delete m_methodAsm;
	m_methodAsm = 0;
}

void ClassAndMethodContext::enterScope()
{
	m_scopeStack.push_back(new LocalVariableScope(int8_t(m_numLocals)));
}

void ClassAndMethodContext::leaveScope()
{
	assert(!m_scopeStack.empty());
	LocalVariableScope *prevScope = m_scopeStack.back();
	m_scopeStack.pop_back();
	if (prevScope->getMaxLocalVars() > m_numLocals) {
		m_numLocals = prevScope->getMaxLocalVars();
	}
	delete prevScope;
}

void ClassAndMethodContext::endOfParameters()
{
	m_endOfParameters = true;
}

int8_t ClassAndMethodContext::addLocalVar(const std::wstring &varName, const Location &varLoc)
{
	assert(!m_scopeStack.empty());

	// add local var to current scope
	int8_t local = m_scopeStack.back()->addLocalVar(varName, varLoc);

	// if end of method parameters hasn't been reached yet,
	// add the variable to the method as a parameter
	if (!m_endOfParameters) {
		m_methodAsm->addParam(varName);
	}

	return local;
}

int8_t ClassAndMethodContext::getLocalVar(const std::wstring &varName, const Location &refLoc) const
{
	assert(!m_scopeStack.empty());

	int local = lookupLocalVar(varName);
	if (local >= 0) {
		return int8_t(local);
	}

	// Undefined local variable
	throw SemanticAnalysisException(L"Undefined local variable " + varName, refLoc);
}

bool ClassAndMethodContext::localVarDefined(const std::wstring &varName) const
{
	assert(!m_scopeStack.empty());

	return lookupLocalVar(varName) >= 0;
}

bool ClassAndMethodContext::currentClassContainsField(const std::wstring &fieldName) const
{
	if (m_clsASTNode == 0) {
		// this is the Main$ class, which does not have any fields
		return false;
	}

	const ASTNode::ChildList& memberList = m_clsASTNode->getChildList().at(0)->getChildList();

	for (ASTNode::ChildList::const_iterator i = memberList.begin(); i != memberList.end(); i++) {
		if ((*i)->getType() == AST_FIELD && (*i)->getStringValue() == fieldName) {
			return true;
		}
	}

	return false;
}

int ClassAndMethodContext::lookupLocalVar(const std::wstring &varName) const
{
	assert(!m_scopeStack.empty());

	// Try scopes starting with current one,
	// working towards outer scopes
	int i = int(m_scopeStack.size() - 1);
	while (i >= 0) {
		if (m_scopeStack[i]->localVarDefined(varName)) {
			return m_scopeStack[i]->getLocalVar(varName);
		}
		i--;
	}

	return -1;
}

// ----------------------------------------------------------------------
// Compiler class
// ----------------------------------------------------------------------

Compiler::Compiler(CodeBase *vm)
	: m_vm(vm)
{
	// Add the default implicit static module imports
	m_staticImportMap[L"print"] =L"System::IO";
	m_staticImportMap[L"println"] = L"System::IO";
}

Compiler::~Compiler()
{
}

void Compiler::compileSource(const std::string &fileName, CodeBase *codeBase)
{
	Parser parser;
	parser.initialize(InputStreamReader::readFile(fileName));
	parser.parse();

	Compiler comp(codeBase);
	ASTNode *ast = parser.getAST();
	try {
		comp.compile(ast);
	} catch (...) {
		ASTNode::destroy(ast);
		throw;
	}
}

void Compiler::compile(ASTNode *parseTree)
{
	parseTree->accept(this);
}

void Compiler::visitStatementList(ASTNode * n)
{
	bool addedMainClassAndMethod = false;

	if (m_contextStack.size() == 0 && containsExecutableStatements(n)) {
		// This translation unit has executable statements that are not
		// contained in an explicit class.  Create a Main$
		// class and main$ method.
		addedMainClassAndMethod = true;
		enterClass(L"Main$", 0);
		getTopContext()->enterMethod(L"main$", 0, n->getLocation());
		getTopContext()->endOfParameters(); // main$ has no parameters
	}

	Base::visitStatementList(n); // visit children

	if (addedMainClassAndMethod) {
		getTopContext()->leaveMethod();
		leaveClass();
	}
}

bool Compiler::containsExecutableStatements(ASTNode *n)
{
	for (ASTNode::ChildList::iterator i = n->getChildList().begin(); i != n->getChildList().end(); i++) {
		ASTNode *child = *i;
		if (child->isExecutableStatement()) {
			return true;
		}
	}
	return false;
}

void Compiler::visitStatement(ASTNode * n)
{
	// compile the expression
	Base::visitStatement(n);

	// pop the result of the expression off the operand stack
	getTopContext()->getMethodAssembler()->emitInstruction(OP_POP);
}

void Compiler::visitWhileStatement(ASTNode * n)
{
	//printf("start while...\n");

	ClassAndMethodContext *context = getTopContext();

	// emit GOTO to jump to loop condition
	std::wstring condLabel = context->getMethodAssembler()->emitBranchInstruction(OP_GOTO);

	// create label at top of loop body
	std::wstring topLabel = context->getMethodAssembler()->genLabel();
	context->getMethodAssembler()->defineLabel(topLabel);

	// compile loop body
	context->enterScope();
	n->getChildList().at(1)->accept(this);
	context->leaveScope();

	// define label for loop condition
	context->getMethodAssembler()->defineLabel(condLabel);

	// compile condition
	n->getChildList().at(0)->accept(this);

	// if condition is true, jump back to top of loop
	context->getMethodAssembler()->emitBranchInstruction(OP_IFTRUE, topLabel);

	//printf("done with while...\n");
}

void Compiler::visitIfStatement(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();

	// compile condition
	n->getChildList().at(0)->accept(this);

	std::wstring ifFalseLabel = context->getMethodAssembler()->emitBranchInstruction(OP_IFFALSE);

	// compile if-true block
	context->enterScope();
	n->getChildList().at(1)->accept(this);
	context->leaveScope();

	if (n->getChildList().size() == 2) {
		// there is no if-false block:
		// define label where if-true and if-false paths join
		context->getMethodAssembler()->defineLabel(ifFalseLabel);
	} else {
		// emit GOTO instruction at end of if-true block
		std::wstring doneLabel = context->getMethodAssembler()->emitBranchInstruction(OP_GOTO);

		// define label where if-false block begins
		context->getMethodAssembler()->defineLabel(ifFalseLabel);

		// compile if-false block
		context->enterScope();
		n->getChildList().at(2)->accept(this);
		context->leaveScope();

		// define label where if-true and if-false paths join
		context->getMethodAssembler()->defineLabel(doneLabel);
	}
}

void Compiler::visitBinaryOpExpr(ASTNode * n)
{
	int32_t op = n->getIntValue();

	if (op == ASSIGN) {
		ASTNode *left = n->getChildList().at(0);
		ASTNodeType lvalueType = left->getType();
		if (lvalueType != AST_VAR_REF_EXPR && lvalueType != AST_FIELD_REF_EXPR) {
			throw new SemanticAnalysisException(L"non-lvalue in assignment", n->getLocation());
		}

		ASTNode *right = n->getChildList().at(1);

		if (lvalueType == AST_FIELD_REF_EXPR) {
			// compile the expression that computes the reference
			// to the object whose field is being assigned.
			// don't compile the actual field reference expr though -
			// that would result in a LOAD of the field.
			left->getChildList().at(0)->accept(this);

			// compile the expression which computes the value being stored
			right->accept(this);

			// add or create symbol reference for field name
			ConstantPool *cp = getTopContext()->getClass()->getConstantPool();
			uint16_t symRef =
					cp->createConstantSymbol(left->getStringValue());

			// emit the STORE_FIELD instruction
			getTopContext()->getMethodAssembler()->emitCPInstruction(OP_STORE_FIELD, symRef);

			// stored value is left on the stack
		} else {
			// store in local variable
			const std::wstring &varName = left->getStringValue();

			if (getTopContext()->localVarDefined(varName)) {
				// compile the expression which computes the value being stored
				right->accept(this);

				// store into local variable
				int8_t local = getTopContext()->getLocalVar(varName, left->getLocation());
				getTopContext()->getMethodAssembler()->emitI8Instruction(OP_STORE_LOCAL, local);
			} else if (getTopContext()->currentClassContainsField(varName)) {
				// implicit reference to instance field

				// load reference to receiver object
				getTopContext()->getMethodAssembler()->emitI8Instruction(OP_LOAD_LOCAL, 0);

				// compile the expression which computes the value being stored
				right->accept(this);

				// emit field store instruction
				uint16_t fieldRef = getTopContext()->getClass()->getConstantPool()->createConstantSymbol(varName);
				getTopContext()->getMethodAssembler()->emitCPInstruction(OP_STORE_FIELD, fieldRef);
			} else {
				// no local variable or field of appropriate name -
				// throw exception
				getTopContext()->getLocalVar(varName, n->getLocation());
			}
		}

		// Note: both field stores and local variable stores
		// result in the stored value being left on the operand stack.
		// This directly handles the case where the assignment is used
		// to compute a value.
	} else if (op == LOGICAL_AND || op == LOGICAL_OR) {
		std::wstring outLabel = getTopContext()->getMethodAssembler()->genLabel();

		// compile left subexpr
		n->getChildList().at(0)->accept(this);

		// dup top value (in case we're going to short circuit out early)
		getTopContext()->getMethodAssembler()->emitInstruction(OP_DUP);

		// if value is false for &&, or true for ||, short circuit
		getTopContext()->getMethodAssembler()->emitBranchInstruction(
				op == LOGICAL_AND ? OP_IFFALSE : OP_IFTRUE, outLabel);

		// pop value of left subexpr
		getTopContext()->getMethodAssembler()->emitInstruction(OP_POP);

		// compile right subexpr: its value will become the overall value
		// of the expression
		n->getChildList().at(1)->accept(this);

		getTopContext()->getMethodAssembler()->defineLabel(outLabel);
	} else {
		// compile subexpressions
		Base::visitBinaryOpExpr(n);

		// Emit method call on left-hand operand
		const wchar_t *opName;
		switch (op) {
#if 0
		case LOGICAL_AND:
			opName = L"&&"; break; // FIXME: short circuiting should be implemented in the generated code
		case LOGICAL_OR:
			opName = L"||"; break; // FIXME: short circuiting
#endif
		case EQUALS:
			opName = L"=="; break;
		case NOT_EQUALS:
			opName = L"!="; break;
		case LESS_THAN:
			opName = L"<"; break;
		case LESS_THAN_EQ:
			opName = L"<="; break;
		case GREATER_THAN:
			opName = L">"; break;
		case GREATER_THAN_EQ:
			opName = L">="; break;
		case PLUS:
			opName = L"+"; break;
		case MINUS:
			opName = L"-"; break;
		case TIMES:
			opName = L"*"; break;
		case DIVIDE:
			opName = L"/"; break;
		default:
			throw Exception(L"unknown operator");
		}
		uint16_t symRef = getTopContext()->getClass()->getConstantPool()->createConstantSymbol(opName);
		getTopContext()->getMethodAssembler()->emitCPI8Instruction(OP_CALL, symRef, 2);
	}
}

void Compiler::visitUnaryOpExpr(ASTNode * n)
{
	// compile subexpression
	Base::visitChildren(n);

	// emit call to operator
	int32_t op = n->getIntValue();

	const wchar_t *opName;

	switch (op) {
	case NOT:
		opName = L"!";
		break;
	// TODO: other unary operators
	default:
		throw Exception(L"Unknown operator");
	}

	uint16_t symRef = getTopContext()->getClass()->getConstantPool()->createConstantSymbol(opName);
	getTopContext()->getMethodAssembler()->emitCPI8Instruction(OP_CALL, symRef, 1);
}

void Compiler::visitFieldRefExpr(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();
	ConstantPool *cp = context->getClass()->getConstantPool();

	// compile subexpression computing reference to the object
	Base::visitFieldRefExpr(n);

	// add or create ConstantSymbol with name of field
	uint16_t symRef = cp->createConstantSymbol(n->getStringValue());

	// emit field ref instruction
	context->getMethodAssembler()->emitCPInstruction(OP_LOAD_FIELD, symRef);
}

void Compiler::visitMethodCallExpr(ASTNode * n)
{
	// compile expression which yields the reference to the
	// receiver object
	n->getChildList().at(0)->accept(this);

	// compile argument expressions and emit the CALL instruction
	compileMethodCall(n->getChildList().at(1), n->getStringValue(), n->getLocation());
}

void Compiler::visitModuleMethodCallExpr(ASTNode * n)
{
	//throw SemanticAnalysisException(L"static calls to module methods not supported yet", n->getLocation());

	const std::wstring &methodName = n->getStringValue();

	// look up in map of static imports
	ImportMap::iterator i = m_staticImportMap.find(methodName);
	if (i == m_staticImportMap.end()) {
		throw SemanticAnalysisException(L"Reference to undefined module method " + methodName, n->getLocation());
	}

	// get fully-qualified name of class containing imported method
	const std::wstring &targetCls = (*i).second;

	// add class constant
	ClassAndMethodContext *context = getTopContext();
	ConstantPool *cp = context->getClass()->getConstantPool();
	uint16_t clsRef = cp->createConstantClass(targetCls);

	// emit PUSH_MODULE_REF instruction
	context->getMethodAssembler()->emitCPInstruction(OP_PUSH_MODULE_REF, clsRef);

	// compile method call
	compileMethodCall(n->getChildList().at(0), methodName, n->getLocation());
}

void Compiler::visitVarRefExpr(ASTNode * n)
{
	// NOTE: this method should only be called for loads
	// of variables, not stores.  (Assignments are handled
	// specially by requiring a var ref or field ref on the
	// left hand side.)

	ClassAndMethodContext *context = getTopContext();

	const std::wstring &varName = n->getStringValue();

	if (context->localVarDefined(varName)) {
		// reference to local variable
		int8_t local = context->getLocalVar(n->getStringValue(), n->getLocation());
		context->getMethodAssembler()->emitI8Instruction(OP_LOAD_LOCAL, local);
	} else if (context->currentClassContainsField(varName)) {
		// reference to instance field
		context->getMethodAssembler()->emitI8Instruction(OP_LOAD_LOCAL, 0);
		uint16_t fieldRef = context->getClass()->getConstantPool()->createConstantSymbol(varName);
		context->getMethodAssembler()->emitCPInstruction(OP_LOAD_FIELD, fieldRef);
	} else {
		// reference to module (presumably)
		ImportMap::iterator i = m_importMap.find(varName);
		if (i == m_importMap.end()) {
			// name not defined as either local variable or import
			context->getLocalVar(varName, n->getLocation()); // throw exception
		}

		// get fully-qualified name of imported class
		const std::wstring &targetCls = (*i).second;

		// add class constant
		ConstantPool *cp = context->getClass()->getConstantPool();
		uint16_t clsRef = cp->createConstantClass(targetCls);

		// emit PUSH_MODULE_REF instruction
		context->getMethodAssembler()->emitCPInstruction(OP_PUSH_MODULE_REF, clsRef);
	}
}

void Compiler::visitIntLiteralExpr(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();
	int32_t value = n->getIntValue();

	if (value >= -128 && value < 128) {
		// can emit directly in bytecode
		context->getMethodAssembler()->emitI8Instruction(OP_PUSH_I8_CONST, (int8_t) value);
	} else {
		// Add (or reuse) a ConstantInt32 value.
		ConstantPool *cp = context->getClass()->getConstantPool();
		uint16_t i32Ref = cp->createConstantInt32(value);
		context->getMethodAssembler()->emitCPInstruction(OP_PUSH_CONST, i32Ref);
	}
}

void Compiler::visitBooleanLiteralExpr(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();
	int32_t value = n->getIntValue();

	// Add (or reuse) a ConstantBoolean value.
	ConstantPool *cp = context->getClass()->getConstantPool();
	uint16_t boolRef = cp->createConstantBoolean(value != 0);
	context->getMethodAssembler()->emitCPInstruction(OP_PUSH_CONST, boolRef);
}

void Compiler::visitSymbolLiteralExpr(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();
	ConstantPool *cp = context->getClass()->getConstantPool();

	// Add (or reuse) ConstantSymbol.
	uint16_t symRef = cp->createConstantSymbol(n->getStringValue());

	context->getMethodAssembler()->emitCPInstruction(OP_PUSH_CONST, symRef);
}

void Compiler::visitStringLiteralExpr(ASTNode * n)
{
	ClassAndMethodContext *context = getTopContext();
	ConstantPool *cp = context->getClass()->getConstantPool();

	// Add (or reuse) ConstantString.
	uint16_t strRef = cp->createConstantString(n->getStringValue());

	context->getMethodAssembler()->emitCPInstruction(OP_PUSH_CONST, strRef);
}

void Compiler::visitSelfRefExpr(ASTNode * n)
{
	MethodAssembler *methodAsm = getTopContext()->getMethodAssembler();

	// the self reference is stored in local variable 0
	methodAsm->emitI8Instruction(OP_LOAD_LOCAL, int8_t(0));
}

void Compiler::visitNewExpr(ASTNode * n)
{
	// add or create a ConstantClass for the classname
	ClassAndMethodContext *context = getTopContext();
	uint16_t clsRef = context->getClass()->getConstantPool()->createConstantClass(n->getStringValue());

	// emit the NEW opcode
	context->getMethodAssembler()->emitCPInstruction(OP_NEW, clsRef);

	// emit the DUP opcode
	context->getMethodAssembler()->emitInstruction(OP_DUP);

	// emit the call to the init() method,
	// and clear its return value
	compileMethodCall(n->getChildList().at(0), L"init", n->getLocation());
	context->getMethodAssembler()->emitInstruction(OP_POP);
}

void Compiler::visitClass(ASTNode * n)
{
    compileClassOrModule(n);
}

void Compiler::visitModule(ASTNode * n)
{
	compileClassOrModule(n);
}
//	void visitMemberList(ASTNode * n);

void Compiler::visitMagicMethod(ASTNode *n)
{
	compileMethod(n, METHOD_FLAG_MAGIC);
}

void Compiler::visitMethod(ASTNode * n)
{
    compileMethod(n, 0);
}

void Compiler::visitField(ASTNode * n)
{
	assert(m_contextStack.size() > 0);
	Field *f = new Field(n->getStringValue());
	getTopContext()->getClass()->addAndAdoptField(f);
}

void Compiler::visitVar(ASTNode * n)
{
#ifdef DEBUG_VARREF
	printf("visiting var!\n");
#endif

	getTopContext()->addLocalVar(n->getStringValue(), n->getLocation());

	if (n->getChildList().size() > 0) {
		// this is a variable declaration with an initialization

		// generate code for expression computing the value
		n->getChildList().at(0)->accept(this);

		// store
		getTopContext()->getMethodAssembler()->emitI8Instruction(
				OP_STORE_LOCAL,
				getTopContext()->getLocalVar(n->getStringValue(), n->getLocation()));

		// since stores leave the stored value on the stack, pop it off
		getTopContext()->getMethodAssembler()->emitInstruction(OP_POP);
	}
}

//	void visitArgList(ASTNode * n);
//	void visitUnit(ASTNode * n);
//	void visitImportList(ASTNode * n);
void Compiler::visitImport(ASTNode * n)
{
	const Import *import = n->getImport();
	m_importMap[import->getAlias()] = import->getImportClsName();
}

void Compiler::visitStaticImport(ASTNode *n)
{
	const Import *import = n->getImport();
	m_staticImportMap[import->getAlias()] = import->getImportClsName();
}

void Compiler::visitReturnStatement(ASTNode * n)
{
	// compile subexpression
	Base::visitReturnStatement(n);

	// emit RETURN instruction
	getTopContext()->getMethodAssembler()->emitInstruction(OP_RETURN);
}

void Compiler::visitNilExpr(ASTNode * n)
{
	getTopContext()->getMethodAssembler()->emitInstruction(OP_PUSH_NIL);
}

void Compiler::enterClass(const std::wstring &clsName, ASTNode *clsASTNode)
{
	ClassAndMethodContext *context = new ClassAndMethodContext(clsName, m_vm, clsASTNode);
	m_contextStack.push_back(context);
}

void Compiler::leaveClass()
{
	assert(!m_contextStack.empty());
	ClassAndMethodContext *context = m_contextStack.back();
	m_contextStack.pop_back();
	delete context;
}

ClassAndMethodContext *Compiler::getTopContext()
{
	assert(!m_contextStack.empty());
	return m_contextStack.back();
}

void Compiler::compileClassOrModule(ASTNode *n)
{
    if(m_contextStack.size() > 0 && m_contextStack.back()->getClass()->getClassName() != L"Main$"){
        throw SemanticAnalysisException(L"Nested classes are not allowed", n->getLocation());
    }
    enterClass(n->getStringValue(), n);

    if (n->getType() == AST_MODULE) {
    	getTopContext()->getClass()->setFlags(CLASS_FLAG_MODULE);
    }

    Base::visitChildren(n);

    // all classes must have an init() method,
    // so generate one if one wasn't defined explicitly
    if (!getTopContext()->getClass()->hasMethod(L"init")) {
    	getTopContext()->enterMethod(L"init", 0, n->getLocation());
    	getTopContext()->endOfParameters(); // no parameters (other than implicit receiver reference)
    	getTopContext()->getMethodAssembler()->emitInstruction(OP_PUSH_NIL);
    	getTopContext()->getMethodAssembler()->emitInstruction(OP_RETURN);
    	getTopContext()->leaveMethod();
    }

    leaveClass();
}

void Compiler::compileMethod(ASTNode *n, uint32_t flags)
{
	assert(m_contextStack.size() > 0);

	getTopContext()->enterMethod(n->getStringValue(), flags, n->getLocation());

	// Add parameters
	n->getChildList().at(0)->accept(this);
	getTopContext()->endOfParameters();

	if (flags & METHOD_FLAG_MAGIC) {
		// Set MAGIC flag in class flags
		Class *cls = getTopContext()->getClass();
		uint32_t clsFlags = cls->getFlags();
		clsFlags |= CLASS_FLAG_MAGIC;
		cls->setFlags(clsFlags);
	} else {
		// Compile method body
		n->getChildList().at(1)->accept(this);
	}

	getTopContext()->leaveMethod();
}

// This method assumes that the code to yield the reference
// to the receiver object has already been compiled.
void Compiler::compileMethodCall(ASTNode *argList, const std::wstring &methodName, const Location &loc)
{
	ClassAndMethodContext *context = getTopContext();
	ConstantPool *cp = context->getClass()->getConstantPool();

	// compile argument expressions
	visitChildren(argList);

	// check that the number of arguments can fit in an int8_t
	uint32_t numExplicitArgs = argList->getChildList().size();
	uint32_t numArgs = numExplicitArgs + 1; // the receiver object is considered an argument
	if (numArgs >= METHOD_MAX_LOCALS) {
		throw SemanticAnalysisException(L"Too many arguments in method call", loc);
	}

	// add or create ConstantSymbol with name of method
	uint16_t symRef = cp->createConstantSymbol(methodName);

	// emit call instruction
	context->getMethodAssembler()->emitCPI8Instruction(OP_CALL, symRef, int8_t(numArgs));

}
